#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
IAP工具 简化GUI版本 - 通过CAN总线更新STM32固件

此脚本提供简化的图形用户界面，用于通过CAN总线向STM32发送固件更新命令和数据。
使用前请确保已安装PCANBasic库，并且CAN适配器已正确连接。
"""

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import threading
import os
import sys
import time
import struct

try:
    from PCANBasic import *
except ImportError:
    messagebox.showerror("错误", "未找到PCANBasic库，请确保已安装PCAN驱动")
    sys.exit(1)

# IAP命令定义
IAP_CMD_CONNECT = 0x01
IAP_CMD_ERASE = 0x02
IAP_CMD_WRITE = 0x03
IAP_CMD_VERIFY = 0x04
IAP_CMD_RESET = 0x05
IAP_CMD_GET_INFO = 0x06
IAP_CMD_DATA_ACK = 0x07

# IAP响应码
IAP_ACK_SUCCESS = 0x00
IAP_ACK_CONNECT_OK = 0x01
IAP_ACK_ERASE_OK = 0x02
IAP_ACK_WRITE_OK = 0x03
IAP_ACK_VERIFY_OK = 0x04
IAP_ACK_RESET_OK = 0x05
IAP_ACK_INFO = 0x06
IAP_ACK_DATA_OK = 0x07

# CAN帧ID
IAP_CAN_CMD_ID = 0x100
IAP_CAN_DATA_ID = 0x101
IAP_CAN_RESP_ID = 0x102

# 应用程序起始地址
APP_START_ADDR = 0x08007000

class SimpleIAPTool:
    def __init__(self, root):
        self.root = root
        self.root.title("IAP固件更新工具")
        self.root.geometry("600x800")
        self.root.resizable(False, True)
        
        # 初始化变量
        self.pcan = None
        self.app_addr = APP_START_ADDR
        self.timeout = 5.0
        self.is_connected = False
        self.is_updating = False
        self.available_ports = []
        
        # 创建界面
        self.create_widgets()
        
        # 初始化端口列表
        self.refresh_ports()
    
    def create_widgets(self):
        """创建界面组件"""
        # 主框架
        main_frame = ttk.Frame(self.root, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 标题
        title_label = ttk.Label(main_frame, text="STM32 IAP固件更新工具", 
                               font=('Arial', 16, 'bold'))
        title_label.pack(pady=(0, 20))
        
        # 连接设置
        conn_frame = ttk.LabelFrame(main_frame, text="连接设置", padding="10")
        conn_frame.pack(fill=tk.X, pady=(0, 15))
        
        # CAN端口设置
        port_frame = ttk.Frame(conn_frame)
        port_frame.pack(fill=tk.X, pady=(0, 10))
        
        ttk.Label(port_frame, text="CAN端口:").pack(side=tk.LEFT)
        self.port_var = tk.StringVar()
        self.port_combo = ttk.Combobox(port_frame, textvariable=self.port_var, width=15, state="readonly")
        self.port_combo.pack(side=tk.LEFT, padx=(10, 10))
        
        refresh_btn = ttk.Button(port_frame, text="刷新", command=self.refresh_ports, width=8)
        refresh_btn.pack(side=tk.LEFT, padx=(0, 20))
        
        ttk.Label(port_frame, text="波特率:").pack(side=tk.LEFT)
        self.bitrate_var = tk.StringVar(value="500000")
        bitrate_combo = ttk.Combobox(port_frame, textvariable=self.bitrate_var, 
                                   values=["125000", "250000", "500000", "1000000"], 
                                   width=12, state="readonly")
        bitrate_combo.pack(side=tk.LEFT, padx=(10, 0))
        
        # 连接按钮
        self.connect_btn = ttk.Button(conn_frame, text="连接设备", 
                                    command=self.toggle_connection)
        self.connect_btn.pack(pady=(10, 0))
        
        # 固件文件选择
        file_frame = ttk.LabelFrame(main_frame, text="固件文件", padding="10")
        file_frame.pack(fill=tk.X, pady=(0, 15))
        
        file_select_frame = ttk.Frame(file_frame)
        file_select_frame.pack(fill=tk.X, pady=(0, 10))
        
        self.file_var = tk.StringVar()
        file_entry = ttk.Entry(file_select_frame, textvariable=self.file_var)
        file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
        
        browse_btn = ttk.Button(file_select_frame, text="浏览", command=self.browse_file)
        browse_btn.pack(side=tk.RIGHT)
        
        # 文件信息
        self.file_info_var = tk.StringVar(value="未选择文件")
        info_label = ttk.Label(file_frame, textvariable=self.file_info_var, 
                              foreground="gray")
        info_label.pack(anchor=tk.W)
        
        # 操作按钮
        btn_frame = ttk.LabelFrame(main_frame, text="操作", padding="10")
        btn_frame.pack(fill=tk.X, pady=(0, 15))
        
        btn_grid = ttk.Frame(btn_frame)
        btn_grid.pack()
        
        self.info_btn = ttk.Button(btn_grid, text="获取设备信息", 
                                 command=self.get_device_info, state="disabled")
        self.info_btn.grid(row=0, column=0, padx=(0, 10), pady=(0, 10))
        
        self.erase_btn = ttk.Button(btn_grid, text="擦除Flash", 
                                  command=self.erase_flash, state="disabled")
        self.erase_btn.grid(row=0, column=1, padx=(0, 10), pady=(0, 10))
        
        self.update_btn = ttk.Button(btn_grid, text="更新固件", 
                                   command=self.update_firmware, state="disabled")
        self.update_btn.grid(row=1, column=0, padx=(0, 10))
        
        self.reset_btn = ttk.Button(btn_grid, text="复位设备", 
                                  command=self.reset_device, state="disabled")
        self.reset_btn.grid(row=1, column=1, padx=(0, 10))
        
        # 进度显示
        progress_frame = ttk.LabelFrame(main_frame, text="进度", padding="10")
        progress_frame.pack(fill=tk.X, pady=(0, 15))
        
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, 
                                          maximum=100)
        self.progress_bar.pack(fill=tk.X, pady=(0, 5))
        
        self.status_var = tk.StringVar(value="就绪")
        status_label = ttk.Label(progress_frame, textvariable=self.status_var)
        status_label.pack(anchor=tk.W)
        
        # 状态显示
        status_frame = ttk.LabelFrame(main_frame, text="状态", padding="10")
        status_frame.pack(fill=tk.BOTH, expand=True)
        
        self.log_text = tk.Text(status_frame, height=8, state="disabled", 
                               wrap=tk.WORD)
        scrollbar = ttk.Scrollbar(status_frame, orient="vertical", 
                                command=self.log_text.yview)
        self.log_text.configure(yscrollcommand=scrollbar.set)
        
        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    
    def log_message(self, message):
        """添加日志消息"""
        def add_log():
            self.log_text.config(state="normal")
            timestamp = time.strftime("%H:%M:%S")
            self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
            self.log_text.see(tk.END)
            self.log_text.config(state="disabled")
        
        if threading.current_thread() == threading.main_thread():
            add_log()
        else:
            self.root.after(0, add_log)
    
    def update_progress(self, value, text=""):
        """更新进度"""
        def update():
            self.progress_var.set(value)
            if text:
                self.status_var.set(text)
        
        if threading.current_thread() == threading.main_thread():
            update()
        else:
            self.root.after(0, update)
    
    def update_ui_state(self, connected=None, updating=None):
        """更新UI状态"""
        def update():
            if connected is not None:
                self.is_connected = connected
                if connected:
                    self.connect_btn.config(text="断开连接")
                    self.info_btn.config(state="normal")
                    self.erase_btn.config(state="normal")
                    self.update_btn.config(state="normal")
                    self.reset_btn.config(state="normal")
                else:
                    self.connect_btn.config(text="连接设备")
                    self.info_btn.config(state="disabled")
                    self.erase_btn.config(state="disabled")
                    self.update_btn.config(state="disabled")
                    self.reset_btn.config(state="disabled")
            
            if updating is not None:
                self.is_updating = updating
                if updating:
                    self.connect_btn.config(state="disabled")
                    self.info_btn.config(state="disabled")
                    self.erase_btn.config(state="disabled")
                    self.update_btn.config(state="disabled")
                    self.reset_btn.config(state="disabled")
                else:
                    self.connect_btn.config(state="normal")
                    if self.is_connected:
                        self.info_btn.config(state="normal")
                        self.erase_btn.config(state="normal")
                        self.update_btn.config(state="normal")
                        self.reset_btn.config(state="normal")
        
        if threading.current_thread() == threading.main_thread():
            update()
        else:
            self.root.after(0, update)
    
    def refresh_ports(self):
        """刷新可用端口列表"""
        try:
            # PCAN通道列表
            pcan_channels = [
                "PCAN_USBBUS1", "PCAN_USBBUS2", "PCAN_USBBUS3", "PCAN_USBBUS4",
                "PCAN_USBBUS5", "PCAN_USBBUS6", "PCAN_USBBUS7", "PCAN_USBBUS8",
                "PCAN_USBBUS9", "PCAN_USBBUS10", "PCAN_USBBUS11", "PCAN_USBBUS12",
                "PCAN_USBBUS13", "PCAN_USBBUS14", "PCAN_USBBUS15", "PCAN_USBBUS16",
                "PCAN_PCIBUS1", "PCAN_PCIBUS2", "PCAN_PCIBUS3", "PCAN_PCIBUS4",
                "PCAN_PCIBUS5", "PCAN_PCIBUS6", "PCAN_PCIBUS7", "PCAN_PCIBUS8",
                "PCAN_PCIBUS9", "PCAN_PCIBUS10", "PCAN_PCIBUS11", "PCAN_PCIBUS12",
                "PCAN_PCIBUS13", "PCAN_PCIBUS14", "PCAN_PCIBUS15", "PCAN_PCIBUS16",
                "PCAN_PCCBUS1", "PCAN_PCCBUS2",
                "PCAN_LANBUS1", "PCAN_LANBUS2", "PCAN_LANBUS3", "PCAN_LANBUS4",
                "PCAN_LANBUS5", "PCAN_LANBUS6", "PCAN_LANBUS7", "PCAN_LANBUS8"
            ]
            
            self.available_ports = pcan_channels
            
            # 更新下拉列表
            self.port_combo['values'] = self.available_ports
            
            # 设置默认选择为PCAN_USBBUS1
            if "PCAN_USBBUS1" in self.available_ports:
                self.port_var.set("PCAN_USBBUS1")
            elif self.available_ports:
                self.port_var.set(self.available_ports[0])
            
            self.log_message(f"已刷新端口列表，找到 {len(self.available_ports)} 个端口")
            
        except Exception as e:
            self.log_message(f"刷新端口列表失败: {e}")
            # 设置默认PCAN端口列表
            self.available_ports = [
                "PCAN_USBBUS1", "PCAN_USBBUS2", "PCAN_USBBUS3", "PCAN_USBBUS4"
            ]
            self.port_combo['values'] = self.available_ports
            if self.available_ports:
                self.port_var.set(self.available_ports[0])
    
    def browse_file(self):
        """浏览固件文件"""
        filename = filedialog.askopenfilename(
            title="选择固件文件",
            filetypes=[("二进制文件", "*.bin"), ("所有文件", "*.*")]
        )
        if filename:
            self.file_var.set(filename)
            try:
                size = os.path.getsize(filename)
                self.file_info_var.set(f"文件大小: {size} 字节")
            except:
                self.file_info_var.set("无法获取文件信息")
    
    def toggle_connection(self):
        """切换连接状态"""
        if self.is_connected:
            self.disconnect_can()
        else:
            self.connect_can()
    
    def connect_can(self):
        """连接CAN总线"""
        try:
            port = self.port_var.get().strip()
            bitrate = int(self.bitrate_var.get())
            
            if not port:
                messagebox.showerror("错误", "请选择CAN端口")
                return
            
            # 创建PCAN对象
            self.pcan = PCANBasic()
            
            # 获取PCAN通道
            channel = getattr(sys.modules[__name__], port, None)
            if channel is None:
                messagebox.showerror("错误", f"无效的PCAN通道: {port}")
                return
            
            # 获取波特率
            bitrate_const = getattr(sys.modules[__name__], f"PCAN_BAUD_{bitrate//1000}K", None)
            if bitrate_const is None:
                # 如果没有预定义的波特率常量，则使用自定义值
                if bitrate == 1000000:
                    bitrate_const = PCAN_BAUD_1M
                elif bitrate == 800000:
                    bitrate_const = PCAN_BAUD_800K
                elif bitrate == 500000:
                    bitrate_const = PCAN_BAUD_500K
                elif bitrate == 250000:
                    bitrate_const = PCAN_BAUD_250K
                elif bitrate == 125000:
                    bitrate_const = PCAN_BAUD_125K
                elif bitrate == 100000:
                    bitrate_const = PCAN_BAUD_100K
                elif bitrate == 50000:
                    bitrate_const = PCAN_BAUD_50K
                elif bitrate == 20000:
                    bitrate_const = PCAN_BAUD_20K
                elif bitrate == 10000:
                    bitrate_const = PCAN_BAUD_10K
                else:
                    messagebox.showerror("错误", f"不支持的波特率: {bitrate}")
                    return
            
            self.log_message(f"正在连接PCAN总线: {port}")
            
            # 初始化PCAN通道
            result = self.pcan.Initialize(channel, bitrate_const)
            if result != PCAN_ERROR_OK:
                error_text = self.pcan.GetErrorText(result)
                error_msg = error_text[1].decode('utf-8') if isinstance(error_text[1], bytes) else error_text[1]
                raise Exception(f"初始化失败: {error_msg}")
            
            self.log_message(f"已连接到PCAN设备: {port}, 波特率: {bitrate}")
            self.update_ui_state(connected=True)
            
        except Exception as e:
            messagebox.showerror("连接失败", f"连接PCAN设备失败: {e}")
            self.log_message(f"连接PCAN设备失败: {e}")
            if self.pcan:
                self.pcan.Uninitialize(channel)
                self.pcan = None
    
    def disconnect_can(self):
        """断开CAN总线连接"""
        if self.pcan:
            port = self.port_var.get().strip()
            channel = getattr(sys.modules[__name__], port, PCAN_NONEBUS)
            self.pcan.Uninitialize(channel)
            self.pcan = None
            self.log_message("已断开PCAN总线连接")
        self.update_ui_state(connected=False)
    
    def send_message(self, can_id, data):
        """发送CAN消息"""
        if not self.pcan:
            return False
            
        try:
            # 创建消息
            msg = TPCANMsg()
            msg.ID = can_id
            msg.MSGTYPE = PCAN_MESSAGE_STANDARD
            msg.LEN = min(8, len(data))
            
            # 填充数据
            for i in range(msg.LEN):
                msg.DATA[i] = data[i]
            
            # 保存发送的数据包到txt文件
            log_data = bytearray(8)
            for i in range(8):
                if i < len(data):
                    log_data[i] = data[i]
                else:
                    log_data[i] = 0x00
                    
            log_entry = f"id :{can_id:02X}, receive: {log_data[0]:02X} {log_data[1]:02X} {log_data[2]:02X} {log_data[3]:02X} {log_data[4]:02X} {log_data[5]:02X} {log_data[6]:02X} {log_data[7]:02X}\r\n"
            
            # 写入日志文件
            try:
                with open("can_log.txt", "a") as log_file:
                    log_file.write(log_entry)
            except Exception as e:
                print(f"写入日志文件失败: {e}")
            
            # 发送消息
            port = self.port_var.get().strip()
            channel = getattr(sys.modules[__name__], port, PCAN_NONEBUS)
            result = self.pcan.Write(channel, msg)
            
            if result != PCAN_ERROR_OK:
                error_text = self.pcan.GetErrorText(result)
                error_msg = error_text[1].decode('utf-8') if isinstance(error_text[1], bytes) else error_text[1]
                self.log_message(f"发送消息失败: {error_msg}")
                return False
                
            return True
        except Exception as e:
            self.log_message(f"发送消息异常: {e}")
            return False
    
    def receive_message(self, can_id=None, timeout=None):
        """接收CAN消息"""
        if not self.pcan:
            return None
            
        if timeout is None:
            timeout = self.timeout
            
        try:
            start_time = time.time()
            port = self.port_var.get().strip()
            channel = getattr(sys.modules[__name__], port, PCAN_NONEBUS)
            
            while time.time() - start_time < timeout:
                # 读取消息
                result, msg, timestamp = self.pcan.Read(channel)
                
                if result == PCAN_ERROR_OK:
                    # 检查是否为期望的ID
                    if can_id is None or msg.ID == can_id:
                        return msg
                elif result != PCAN_ERROR_QRCVEMPTY:
                    error_text = self.pcan.GetErrorText(result)
                    error_msg = error_text[1].decode('utf-8') if isinstance(error_text[1], bytes) else error_text[1]
                    self.log_message(f"接收消息错误: {error_msg}")
                    return None
                    
                # 短暂休眠以避免忙等待
                time.sleep(0.001)
                
            return None  # 超时
        except Exception as e:
            self.log_message(f"接收消息异常: {e}")
            return None
    
    def send_command(self, cmd, data=None):
        """发送IAP命令"""
        if data is None:
            data = bytes([0] * 7)
            
        cmd_data = bytes([cmd]) + data
        
        if not self.send_message(IAP_CAN_CMD_ID, cmd_data):
            return False, None
            
        response = self.receive_message(IAP_CAN_RESP_ID)
        if response is None:
            return False, None
            
        return True, response
    
    def get_device_info(self):
        """获取设备信息"""
        def worker():
            self.log_message("正在获取设备信息...")
            success, response = self.send_command(IAP_CMD_GET_INFO)
            
            if not success or response is None:
                self.log_message("获取设备信息失败: 无响应")
                return
                
            response_data = bytes([response.DATA[i] for i in range(response.LEN)])
            
            if response_data[0] == IAP_CMD_GET_INFO and response_data[1] == IAP_ACK_INFO:
                version = f"{response_data[2]}.{response_data[3]}"
                app_addr = (response_data[4] << 24) | (response_data[5] << 16) | (response_data[6] << 8) | response_data[7]
                
                self.log_message(f"设备版本: {version}")
                self.log_message(f"应用程序地址: 0x{app_addr:08X}")
                self.app_addr = app_addr
            else:
                self.log_message(f"获取设备信息失败: 响应码 0x{response_data[1]:02X}")
        
        threading.Thread(target=worker, daemon=True).start()
    
    def erase_flash(self):
        """擦除Flash"""
        def worker():
            self.log_message("正在擦除Flash...")
            self.update_progress(0, "擦除中...")
            success, response = self.send_command(IAP_CMD_CONNECT)
            
            success, response = self.send_command(IAP_CMD_ERASE)
            
            if not success or response is None:
                self.log_message("擦除Flash失败: 无响应")
                self.update_progress(0, "擦除失败")
                return
                
            response_data = bytes([response.DATA[i] for i in range(response.LEN)])
                
            if response_data[0] == IAP_CMD_ERASE and response_data[1] == IAP_ACK_ERASE_OK:
                self.log_message("Flash擦除成功")
                self.update_progress(100, "擦除完成")
            else:
                self.log_message(f"擦除Flash失败: 响应码 0x{response_data[1]:02X}")
                self.update_progress(0, "擦除失败")
        
        threading.Thread(target=worker, daemon=True).start()
    
    def reset_device(self):
        """复位设备"""
        def worker():
            self.log_message("正在复位设备...")
            success, response = self.send_command(IAP_CMD_RESET)
            
            if not success or response is None:
                self.log_message("复位设备失败: 无响应")
                return
                
            response_data = bytes([response.DATA[i] for i in range(response.LEN)])
                
            if response_data[0] == IAP_CMD_RESET and response_data[1] == IAP_ACK_RESET_OK:
                self.log_message("设备复位成功")
            else:
                self.log_message(f"复位设备失败: 响应码 0x{response_data[1]:02X}")
        
        threading.Thread(target=worker, daemon=True).start()
    
    def update_firmware(self):
        """更新固件"""
        firmware_file = self.file_var.get().strip()
        if not firmware_file:
            messagebox.showerror("错误", "请选择固件文件")
            return
            
        if not os.path.exists(firmware_file):
            messagebox.showerror("错误", "固件文件不存在")
            return
        
        if not messagebox.askyesno("确认", "确定要更新固件吗？此操作将覆盖现有固件。"):
            return
        
        def worker():
            self.update_ui_state(updating=True)
            
            try:
                # 读取固件文件
                with open(firmware_file, 'rb') as f:
                    firmware_data = f.read()
                
                firmware_size = len(firmware_data)
                self.log_message(f"开始更新固件: {os.path.basename(firmware_file)}")
                self.log_message(f"固件大小: {firmware_size} 字节")
                
                # 连接设备
                self.update_progress(5, "连接设备...")
                success, response = self.send_command(IAP_CMD_CONNECT)
                if not success or response is None:
                    self.log_message("连接设备失败")
                    return
                    
                response_data = bytes([response.DATA[i] for i in range(response.LEN)])
                if response_data[0] != IAP_CMD_CONNECT or response_data[1] != IAP_ACK_CONNECT_OK:
                    self.log_message("连接设备失败")
                    return
                    
                self.log_message("设备连接成功")
                
                # 擦除Flash
                self.update_progress(10, "擦除Flash...")
                success, response = self.send_command(IAP_CMD_ERASE)
                if not success or response is None:
                    self.log_message("擦除Flash失败")
                    return
                    
                response_data = bytes([response.DATA[i] for i in range(response.LEN)])
                if response_data[0] != IAP_CMD_ERASE or response_data[1] != IAP_ACK_ERASE_OK:
                    self.log_message("擦除Flash失败")
                    return
                    
                self.log_message("Flash擦除成功")
                
                # 发送写入命令
                self.update_progress(15, "准备写入...")
                
                # 确保地址是8字节对齐的
                if self.app_addr % 8 != 0:
                    aligned_addr = (self.app_addr // 8) * 8
                    self.log_message(f"警告：应用程序地址0x{self.app_addr:08X}不是8字节对齐的，已调整为0x{aligned_addr:08X}")
                    self.app_addr = aligned_addr
                
                addr_bytes = struct.pack(">I", self.app_addr)
                size_bytes = struct.pack(">H", firmware_size)
                
                # 计算固件CRC
                firmware_crc = self.calculate_crc(firmware_data)
                
                cmd_data = addr_bytes + size_bytes + bytes([firmware_crc])
                
                success, response = self.send_command(IAP_CMD_WRITE, cmd_data)
                if not success or response is None:
                    self.log_message("发送写入命令失败")
                    return
                    
                response_data = bytes([response.DATA[i] for i in range(response.LEN)])
                if response_data[0] != IAP_CMD_WRITE or response_data[1] != IAP_ACK_SUCCESS:
                    self.log_message("发送写入命令失败")
                    return
                
                # 发送固件数据
                # 确保数据块大小为8字节，符合STM32 Flash写入要求
                chunk_size = 8
                total_chunks = (firmware_size + chunk_size - 1) // chunk_size
                retry_count = 3  # 重试次数
                
                # 如果固件大小不是8的倍数，填充到8的倍数
                if firmware_size % 8 != 0:
                    padding_size = 8 - (firmware_size % 8)
                    firmware_data = firmware_data + bytes([0xFF] * padding_size)
                    self.log_message(f"固件大小不是8的倍数，已填充{padding_size}字节到{len(firmware_data)}字节")
                    firmware_size = len(firmware_data)
                
                # 初始化包序号计数器
                expected_packet_num = 1
                
                for i in range(0, firmware_size, chunk_size):
                    chunk = firmware_data[i:i+chunk_size]
                    progress = 15 + (i // chunk_size) / total_chunks * 70
                    packet_num = i // chunk_size + 1  # 数据包序号，从1开始
                    
                    self.update_progress(progress, f"写入数据: {i+len(chunk)}/{firmware_size} 字节")
                    
                    # 发送数据包并等待确认，支持重试
                    for retry in range(retry_count):
                        if not self.send_message(IAP_CAN_DATA_ID, chunk):
                            if retry == retry_count - 1:
                                self.log_message("发送数据失败，已达到最大重试次数")
                                return
                            self.log_message(f"发送数据包{packet_num}失败，正在重试({retry+1}/{retry_count})")
                            time.sleep(0.01)
                            continue
                            
                        # 等待数据包确认，使用循环确保只接收数据包确认
                        valid_ack = False
                        start_time = time.time()
                        
                        while time.time() - start_time < 1.0:  # 1秒超时
                            response = self.receive_message(IAP_CAN_RESP_ID, timeout=0.1)
                            if response is None:
                                continue
                                
                            response_data = bytes([response.DATA[i] for i in range(response.LEN)])
                            
                            # 只处理数据包确认命令
                            if response_data[0] == IAP_CMD_DATA_ACK and response_data[1] == IAP_ACK_DATA_OK:
                                # 检查包序号
                                resp_packet_num = response_data[2] | (response_data[3] << 8)
                                if resp_packet_num == packet_num:
                                    # 确认成功
                                    valid_ack = True
                                    # 更新期望的下一个包序号
                                    expected_packet_num = resp_packet_num + 1
                                    break
                                else:
                                    self.log_message(f"数据包序号不匹配: 期望{packet_num}，收到{resp_packet_num}")
                            else:
                                # 其他响应，可能是干扰
                                self.log_message(f"收到非预期响应: {response_data[0]:02X} {response_data[1]:02X} (期望数据包确认: {IAP_CMD_DATA_ACK:02X} {IAP_ACK_DATA_OK:02X})")
                                continue
                        
                        if valid_ack:
                            break
                        else:
                            if retry == retry_count - 1:
                                self.log_message(f"数据包{packet_num}确认超时，已达到最大重试次数")
                                return
                            self.log_message(f"数据包{packet_num}确认超时，正在重试({retry+1}/{retry_count})")
                            time.sleep(0.01)
                            continue
                    
                    # 每个包暂停一下，避免设备缓冲区溢出
                    if (packet_num % 64 == 0):
                        time.sleep(0.04)
                
                # 等待编程完成
                self.update_progress(85, "等待编程完成...")
                response = self.receive_message(IAP_CAN_RESP_ID, timeout=30.0)
                if response is None:
                    self.log_message("Flash编程失败: 无响应")
                    return
                    
                response_data = bytes([response.DATA[i] for i in range(response.LEN)])
                if response_data[0] != IAP_CMD_WRITE or response_data[1] != IAP_ACK_WRITE_OK:
                    self.log_message("Flash编程失败")
                    return
                
                self.log_message("Flash编程成功")
                
                # 校验固件
                self.update_progress(90, "校验固件...")
                
                # 重新计算固件CRC，确保与发送前一致
                verify_crc = self.calculate_crc(firmware_data)
                self.log_message(f"本地计算的固件CRC: 0x{verify_crc:02X}")
                
                # 发送校验命令
                success, response = self.send_command(IAP_CMD_VERIFY)
                if not success or response is None:
                    self.log_message("固件校验失败: 无响应")
                    return
                    
                response_data = bytes([response.DATA[i] for i in range(response.LEN)])
                if response_data[0] != IAP_CMD_VERIFY or response_data[1] != IAP_ACK_VERIFY_OK:
                    error_code = response_data[1] if len(response_data) > 1 else 0xFF
                    self.log_message(f"固件校验失败: 错误码 0x{error_code:02X}")
                    
                    # 如果响应包含设备计算的CRC
                    if len(response_data) >= 3:
                        device_crc = response_data[2]
                        self.log_message(f"设备计算的CRC: 0x{device_crc:02X}，本地CRC: 0x{verify_crc:02X}")
                    return
                
                # 如果响应包含设备计算的CRC，进行比较
                if len(response_data) >= 3:
                    device_crc = response_data[2]
                    self.log_message(f"设备计算的CRC: 0x{device_crc:02X}")
                    
                    if device_crc != verify_crc:
                        self.log_message(f"警告：CRC不匹配，但设备报告校验成功")
                
                self.log_message("固件校验成功")
                
                # 添加短暂延迟，确保设备有足够时间处理
                time.sleep(0.5)
                
                # 复位设备
                self.update_progress(95, "复位设备...")
                success, response = self.send_command(IAP_CMD_RESET)
                if success and response:
                    response_data = bytes([response.DATA[i] for i in range(response.LEN)])
                    if response_data[0] == IAP_CMD_RESET and response_data[1] == IAP_ACK_RESET_OK:
                        self.log_message("设备复位成功")
                
                self.update_progress(100, "固件更新完成")
                self.log_message("固件更新成功!")
                
                self.root.after(0, lambda: messagebox.showinfo("成功", "固件更新成功!"))
                
            except Exception as e:
                self.log_message(f"更新固件时发生错误: {e}")
                self.root.after(0, lambda: messagebox.showerror("错误", f"更新固件时发生错误: {e}"))
            finally:
                self.update_ui_state(updating=False)
        
        threading.Thread(target=worker, daemon=True).start()

    def validate_app_address(self):
        """验证应用程序地址的有效性"""
        # 检查地址是否在有效范围内
        if self.app_addr < 0x08000000 or self.app_addr >= 0x08040000:  # STM32G431的Flash范围
            self.app_addr = APP_START_ADDR
            print(f"警告：应用程序地址超出有效范围，已重置为默认值: 0x{self.app_addr:08X}")
        
        # 检查地址是否8字节对齐
        if self.app_addr % 8 != 0:
            aligned_addr = (self.app_addr // 8) * 8
            print(f"警告：应用程序地址0x{self.app_addr:08X}不是8字节对齐的，已调整为0x{aligned_addr:08X}")
            self.app_addr = aligned_addr
    
    def calculate_crc(self, data):
        """计算简单的CRC校验和"""
        crc = 0
        for byte in data:
            crc = (crc + byte) & 0xFF
        return crc
    
    def on_closing(self):
        """窗口关闭事件"""
        if self.pcan:
            port = self.port_var.get().strip()
            channel = getattr(sys.modules[__name__], port, PCAN_NONEBUS)
            self.pcan.Uninitialize(channel)
        self.root.destroy()

def main():
    root = tk.Tk()
    app = SimpleIAPTool(root)
    
    root.protocol("WM_DELETE_WINDOW", app.on_closing)
    root.mainloop()

if __name__ == '__main__':
    main()